//
//  ManagedObjectHelper.m
//  ForTest
//
//  Created by liboxiang on 2019/3/22.
//  Copyright © 2019 liboxiang. All rights reserved.
//

#import "ManagedObjectHelper.h"
#import <objc/runtime.h>

typedef NS_ENUM(NSUInteger,PropertyType) {
    PropertyTypeNone        = 0,
    PropertyTypeNumber      = 1,//整数或者小数
    PropertyTypeBool,
    PropertyTypeString,
    PropertyTypeData,
};

@interface PropertyAttributeModel : NSObject

@property (nonatomic, assign) PropertyType propertyType;

/** 对象类型（如果是基本数据类型，此值为nil） */
@property (nonatomic, strong) Class typeClass;

//属性名
@property (nonatomic, copy) NSString *name;

@end

@implementation PropertyAttributeModel

@end

@implementation ManagedObjectHelper

+ (NSArray *)getPropertyListWithEntity:(Class)entityClass {
    unsigned int outCount = 0;
    NSMutableArray *propertyAttributeModelList = [NSMutableArray arrayWithCapacity:outCount];
    objc_property_t *propertyList = class_copyPropertyList(entityClass, &outCount);
    for (int i = 0; i < outCount; i++) {
        PropertyAttributeModel *model = [PropertyAttributeModel new];
        objc_property_t property = propertyList[i];
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        model.name = propertyName;
        
        const char *type = property_getAttributes(property);
        NSString * typeString = [NSString stringWithUTF8String:type];
        NSArray * attributes = [typeString componentsSeparatedByString:@","];
        NSString * typeAttribute = [attributes objectAtIndex:0];
        NSString * propertyType = [typeAttribute substringFromIndex:1];
        const char * rawPropertyType = [propertyType UTF8String];
        if (strcmp(rawPropertyType, @encode(BOOL)) == 0) {
            //it's a float
            model.propertyType = PropertyTypeBool;
        } else if (strcmp(rawPropertyType, @encode(float)) == 0) {
            //it's an int
            model.propertyType = PropertyTypeNumber;
        } else if (strcmp(rawPropertyType, @encode(double)) == 0) {
            //it's some sort of object
            model.propertyType = PropertyTypeNumber;
        } else if (strcmp(rawPropertyType, @encode(short int)) == 0) {
            model.propertyType = PropertyTypeNumber;
        } else if (strcmp(rawPropertyType, @encode(int)) == 0) {
            model.propertyType = PropertyTypeNumber;
        } else if (strcmp(rawPropertyType, @encode(long)) == 0) {
            model.propertyType = PropertyTypeNumber;
        }
        else if (strcmp(rawPropertyType, @encode(long long)) == 0) {
            model.propertyType = PropertyTypeNumber;
        }
        else {
            NSString * typeClassName = [propertyType substringWithRange:NSMakeRange(2, [propertyType length]-3)];
            Class propertyTypeClass = NSClassFromString(typeClassName);
            if (propertyTypeClass) {
                model.typeClass = propertyTypeClass;
                if ([propertyTypeClass isKindOfClass:[NSString class]]) {
                    model.propertyType = PropertyTypeString;
                }
                else if ([propertyTypeClass isKindOfClass:[NSData class]]) {
                    model.propertyType = PropertyTypeData;
                }
            }
        }
        
        [propertyAttributeModelList addObject:model];
    }
    return propertyAttributeModelList;
}

+ (void)mergeOrAddWithEntityName:(NSString *)entityName list:(NSArray<NSDictionary *>*)list uidKey:(NSString *)uidKey groupDic:(nullable NSDictionary *)groupDic ignoreKeys:(nullable NSArray *)ignoreKeysArray context:(nonnull NSManagedObjectContext *)context{
    Class entityClass = NSClassFromString(entityName);
    NSParameterAssert(entityClass);
    NSParameterAssert(uidKey.length);
    NSParameterAssert(list.count);
    NSParameterAssert(context);
    
    if (!(entityClass && uidKey.length && list.count && context)) {
        return;
    }
    NSArray *propertyList = [self getPropertyListWithEntity:entityClass];
    NSSet *localUIDsSet = [self getUidsWithEntityName:entityName groupDic:groupDic uidKey:uidKey propertyList:propertyList context:context];
    NSMutableArray *toAddArray = [NSMutableArray arrayWithArray:list];
    NSArray *toMergeArray;
    if (localUIDsSet.count) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF[%@] in %@",uidKey,localUIDsSet.allObjects];
        toMergeArray = [list filteredArrayUsingPredicate:predicate];
        toAddArray = [NSMutableArray arrayWithArray:list];
        [toAddArray removeObjectsInArray:toMergeArray];
    }
    
    if (toMergeArray.count) {
        //本地有，合并
        NSMutableSet *uidSet = [NSMutableSet setWithCapacity:0];
        for (NSDictionary *dic in toMergeArray) {
            NSString *uid = [dic objectForKey:uidKey];
            if (uid.length) {
                [uidSet addObject:uid];
            }
        }
        NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.%@ in %@",uidKey,uidSet.allObjects];
        fetchRequest.predicate = predicate;
        NSArray *localModels = [context executeFetchRequest:fetchRequest error:nil];
        for (NSManagedObject *obj in localModels) {
            NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF[%@] = %@",uidKey,[obj valueForKey:uidKey]];
            NSArray *tmp = [toMergeArray filteredArrayUsingPredicate:predicate];
            if (tmp.count) {
                [self setDicValue:tmp.firstObject toModel:obj propertyList:propertyList ignoreKeys:ignoreKeysArray];
            }
        }
        
    }
    if (toAddArray.count) {
        //本地没有，新添加
        for (NSDictionary *dic in toAddArray) {
            NSManagedObject *model = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
            [self setDicValue:dic toModel:model propertyList:propertyList ignoreKeys:nil];
        }
    }
    [context save:nil];
}

+ (void)setDicValue:(NSDictionary *)dic toModel:(NSManagedObject *)model propertyList:(NSArray *)propertyList ignoreKeys:(NSArray *)ignoreKeys{
    for (PropertyAttributeModel *propertyModel in propertyList) {
        if (ignoreKeys.count && [ignoreKeys containsObject:propertyModel.name]) {
            continue;
        }
        else {
            if ([dic.allKeys containsObject:propertyModel.name]) {
                id value = dic[propertyModel.name];
                switch (propertyModel.propertyType) {
                    case PropertyTypeBool: {
                        NSString *lower = [[NSString stringWithFormat:@"%@",value] lowercaseString];
                        if ([lower isEqualToString:@"yes"] || [lower isEqualToString:@"true"]) {
                            value = @YES;
                        } else if ([lower isEqualToString:@"no"] || [lower isEqualToString:@"false"]) {
                            value = @NO;
                        }
                        else {
                            value = [[NSNumberFormatter new] numberFromString:lower];
                        }
                        break;
                    }
                    case PropertyTypeNumber: {
                        value = [[NSNumberFormatter new] numberFromString:[NSString stringWithFormat:@"%@",value]];
                        break;
                    }
                    case PropertyTypeString: {
                        value = [NSString stringWithFormat:@"%@",value];
                        break;
                    }
                    case PropertyTypeData: {
                        if (![value isKindOfClass:[NSData class]]) {
                            value = nil;
                        }
                        break;
                    }
                    default:
                        break;
                }
                if (value) {
                    [model setValue:value forKey:propertyModel.name];
                }
            }
        }
    }
}


+ (NSSet *)getUidsWithEntityName:(NSString *)entityName groupDic:(NSDictionary *)groupDic uidKey:(NSString *)uidKey propertyList:(NSArray *)propertyLilst context:(nonnull NSManagedObjectContext *)context{
    //获取本地存在的groupDic分组的数据的ID
    NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:entityName];
    fetchRequest.resultType = NSDictionaryResultType;
    [fetchRequest setPropertiesToFetch:@[uidKey]];
    
    NSString *predicateStr = @"";
    for (NSString *key in groupDic.allKeys) {
        if (predicateStr.length) {
            predicateStr = [predicateStr stringByAppendingString:@" AND "];
        }
        PropertyType dataType = PropertyTypeNone;
        for (PropertyAttributeModel *model in propertyLilst) {
            if ([model.name isEqualToString:key]) {
                dataType = model.propertyType;
            }
        }
        switch (dataType) {
            case PropertyTypeBool:
            case PropertyTypeNumber: {
                predicateStr = [predicateStr stringByAppendingString:[NSString stringWithFormat:@"SELF.%@ = %@",key,groupDic[key]]];;
                break;
            }
            case PropertyTypeString: {
                predicateStr = [predicateStr stringByAppendingString:[NSString stringWithFormat:@"SELF.%@ = \"%@\"",key,groupDic[key]]];
                break;
            }
            case PropertyTypeData:
            default:
                break;
        }
        
    }

    if (predicateStr.length) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateStr];
        fetchRequest.predicate = predicate;
    }
    
    NSArray *dicArray = [context executeFetchRequest:fetchRequest error:nil];
    NSMutableSet *uidSet = [NSMutableSet setWithCapacity:0];
    for (NSDictionary *dic in dicArray) {
        NSString *uid = [dic objectForKey:uidKey];
        if (uid.length) {
            [uidSet addObject:uid];
        }
    }
    return uidSet;
}

@end
